<?php

class HierarchyTree extends Hierarchy{
    private static $db = array(
        'NLeft' => 'Int',
        'NRight' => 'Int',
        'NLevel' => 'Int',
        'NSeqno' => 'Int'
    );
	
	private static $indexes = array(
        'NLeft' => true,
        'NRight' => true,
        'NLevel' => true,
        'NSeqno' => true,
        'ParentID' => true,
        'MemberID' => true
    );
    
    private static $casting = array(
        "LastEdited" => "SS_Datetime",
        "Created" => "SS_Datetime",
        "TotalDirectDownline" => "Int",
        "TotalGroupDownline" => "Int"
    );
    
    private static $has_one = array('Member' => 'Member');
            
    protected static $creating_order = null;
    
    /**
     * Get an entire branch of the tree in one query.
     */
    public function ChildBranch($filter = null){
        $staged = DataList::create($this->owner->class)
        ->filter('NLeft:GreaterThan', (int)$this->owner->NLeft)
        ->filter('NRight:LessThan', (int)$this->owner->NRight)
        ->exclude('ID', (int)$this->owner->ID);
        
        if($filter){
            $staged = $staged->where($filter);
        }

        if(!$staged) $staged = new DataList();

        return $staged;
    }
    
    function requireDefaultRecords(){
        //only need to pre-order if pre-order doesn't exist yet
        //TODO: allow forcing a re-preording
        if(DataObject::get_one($this->owner->class, "\"NRight\" = 0 OR \"NRight\" IS NULL OR \"NLeft\" = 0 OR \"NLeft\" IS NULL")){ 
            $this->create_preorderings();
            //TODO: output the number of objects updated
        }
    }
    
    
    function create_preorderings(){
        $rootnodes = DataObject::get($this->owner->class, "\"ParentID\" = 0 OR \"ParentID\" IS NULL");
        
        self::$creating_order = true;
        
        $count = 1; //prevents the same orderings beigng used on different trees
        if($rootnodes){
            foreach($rootnodes as $node){
                $count = $node->rebuildTree($count);
            }
        }
        self::$creating_order = false;
    }
    
    /**
     * Recursively sets Left and Right values for each node in the tree.
     */
    function rebuildTree($left = 1, $level = 0){
        
        $this->owner->NLeft = $left;
        $right = $left + 1;
        $level += 1;
        
        $children = $this->owner->AllChildren();
        foreach($children as $child){
            $right = $child->rebuildTree($right, $level);
        }       
        
        $this->owner->NRight = $right;
        $this->owner->NLevel = $level;
        $this->owner->write(); //TODO: this needs to write to live site also
        
        return $right + 1;
    }
    
    /**
     * Update the tree
     * 
     */
    function onBeforeWrite(){
        //New node in the tree
        if(!$this->owner->exists() && !self::$creating_order){
        	$parentRight = 0;
			$parentLevel = 0;
        	if($this->owner->Parent()->exists()){
            	$parentRight = (int)$this->owner->Parent()->NRight;
            	$parentLevel = (int)$this->owner->Parent()->NLevel;
			}
			else if($lastnode = DataObject::get_one($this->owner->class,"ParentID = 0 OR ParentID IS NULL", false, 'Created DESC')){
				$parentRight = (int)$lastnode->NRight + 1;
			}
            
            $baseClass = ClassInfo::baseDataClass($this->owner->class);
            // update left and right values for all nodes
            DB::query(sprintf("UPDATE %s SET NRight = NRight + 2 WHERE NRight >= %s", $baseClass, (int)$parentRight));
			DB::query(sprintf("UPDATE %s SET NLeft = NLeft + 2 WHERE NLeft >= %s", $baseClass, (int)$parentRight));
            
            //set left and right values for this node
            $this->owner->NLeft = $parentRight;
            $this->owner->NRight = $parentRight + 1;
			$this->owner->NLevel = $parentLevel + 1;
        }
		
		if(!$this->owner->ID){
            $this->owner->ID = $this->owner->MemberID;
            $this->owner->Created = date('Y-m-d H:i:s');
        }
        
        if(!$this->owner->NSeqno) {
            $this->owner->NSeqno = $this->owner->Parent()->exists() ? $this->owner->Parent()->TotalDirectDownline + 1 : 1;
        }
		
		if($this->owner->exists() && $this->owner->isChanged('ParentID')){
			$baseClass = ClassInfo::baseDataClass($this->owner->class);
			$origin_left = $this->owner->NLeft;
			$origin_right = $this->owner->NRight;
			$new_parent_right = $this->owner->Parent()->NRight;
			$origin_level = $this->owner->NLevel;
			$new_parent_level = $this->owner->Parent()->NLevel;
			if($new_parent_right < $origin_left){
				$sql = sprintf("UPDATE %s SET 
				NLevel = NLevel + CASE WHEN NLeft BETWEEN %s AND %s - 1 THEN %s - %s + 1 ELSE 0 END,
				NLeft = NLeft + CASE WHEN NLeft BETWEEN %s AND %s THEN %s - %s WHEN NLeft BETWEEN %s AND %s - 1 THEN %s - %s + 1 ELSE 0 END, 
				NRight = NRight + CASE WHEN NRight BETWEEN %s AND %s THEN %s - %s WHEN NRight BETWEEN %s AND %s - 1 THEN %s - %s + 1 ELSE 0 END
				WHERE NLeft BETWEEN %s AND %s OR NRight BETWEEN %s AND %s", $baseClass, $origin_left, $origin_right, $new_parent_level, $origin_level, $origin_left, $origin_right, $new_parent_right, $origin_left, $new_parent_right, $origin_left, $origin_right, $origin_left, $origin_left, $origin_right, $new_parent_right, $origin_left, $new_parent_right, $origin_left, $origin_right, $origin_left, $new_parent_right, $origin_right, $new_parent_right, $origin_right);
				DB::query($sql);
			}
			else if($new_parent_right > $origin_right){
				$sql = sprintf("UPDATE %s SET 
				NLevel = NLevel + CASE WHEN NLeft BETWEEN %s AND %s - 1 THEN %s - %s + 1 ELSE 0 END,
				NLeft = NLeft + CASE WHEN NLeft BETWEEN %s AND %s THEN %s - %s - 1 WHEN NLeft BETWEEN %s + 1 AND %s - 1 THEN %s - %s - 1 ELSE 0 END, 
				NRight = NRight + CASE WHEN NRight BETWEEN %s AND %s THEN %s - %s - 1 WHEN NRight BETWEEN %s + 1 AND %s - 1 THEN %s - %s - 1 ELSE 0 END
				WHERE NLeft BETWEEN %s AND %s OR NRight BETWEEN %s AND %s", $baseClass, $origin_left, $origin_right, $new_parent_level, $origin_level, $origin_left, $origin_right, $new_parent_right, $origin_right, $origin_right, $new_parent_right, $origin_left, $origin_right, $origin_left, $origin_right, $new_parent_right, $origin_right, $origin_right, $new_parent_right, $origin_left, $origin_right, $origin_left, $new_parent_right, $origin_left, $new_parent_right);
				DB::query($sql);
			}
		}

    }
    
    function getTotalDirectDownline(){
        return $this->numChildren();
    }
    
    function getTotalGroupDownline(){
        $left = (int)$this->owner->NLeft;
        $right = (int)$this->owner->NRight;
        return ($right - $left - 1) / 2;
    }
}


?>